Repository navigation
feat: devkitd — Rust coordination daemon + harness ports + CLI bundling - #352
Merged
Merged
Conversation
…ore, event log, discovery, autostart Rust workspace at rust/ with a single-static-binary daemon per the daemon brainstorm: unix socket JSON-RPC (0600 + SO_PEERCRED), rusqlite sole-writer store absorbing the channels/pi-sessions registries, persisted append-only event log with at-least-once subscribe/replay, and a single ps-sweep discovery loop feeding agent.list/agent.appeared/agent.disappeared. TS side: new @ai-devkit/daemon-client package (line JSON-RPC client, tmux-style flock-guarded autostart, platform-binary resolution via AI_DEVKITD_BIN / @ai-devkit/daemon-<plat>-<arch> optional dep / dev target dir). ChannelConfigRepository and the pi-session-tracker extension are daemon-first with file fallback; CLI gains `ai-devkit daemon status|start|stop|logs|install`. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…pawn-lock Post-review remediation: - docs/ai phase docs (requirements/design/planning/implementation/testing) honestly reflecting the built v1 and deviations from the brainstorm. - Rust: proto wire-contract tests, discover golden ps fixture, store concurrent-sole-writer + persistence tests, server end-to-end socket test (replay + live subscribe, malformed-line resilience). - daemon-client: autostart + fallback integration tests against the real binary; vitest config scoping src tests (dist was double-running). - Fix real bug found by the new tests: the spawn lockfile was never unlinked, so one spawn wedged all future autostarts; holder now unlinks, and locks older than 30s are reclaimed. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Console useAgentList now subscribes to daemon events as invalidation signals: refreshes immediately on agent.appeared/disappeared and registry.changed events, and relaxes the 3s poll to a 60s fallback while subscribed. When the daemon is unavailable or subscription fails, the original 3s polling behavior is preserved. The subscription wiring lives in agentListSubscription.ts so it can be unit-tested directly (Ink does not reliably flush useEffect in tests). ensureDaemon() now also skips autostart under vitest to keep tests hermetic. Lifecycle docs rewritten fully self-contained and console migration folded into requirements/design/planning/testing. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…go into nx Rename the daemon binary to devkitd and split the workspace into crates/devkitd (transport/lifecycle) and crates/devkit-core (proto, store, discovery) so future Rust components — harness parsers, tools — have a library to build on. devkit-core's proto types now generate the TS wire types in daemon-client via ts-rs, eliminating hand-mirrored contracts. Nx gains a rust project (nx:run-commands wrapping cargo build/test/clippy/ fmt) so nx run-many covers both languages in one graph. Binary resolution: DEVKITD_BIN is primary, AI_DEVKITD_BIN remains a deprecated alias. Socket/db/log paths are product-scoped and unchanged. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…rness crate, fixture oracle
I0 of the harness-port epic (docs/ai/planning/2026-10-08-epic-harness-port.md):
the substrate every per-harness port iteration builds on. No adapter is
ported yet; agent.enriched returns {agents: [], ported: []} and clients
stay on the local path — zero behavior change.
- devkit-core: EnrichedAgent/EnrichedAgentsResult wire types (serde
camelCase mirrors AgentInfo exactly) + ts-rs bindings into
daemon-client/src/gen
- devkit-harness: SweepContext, HarnessAdapter trait, Registry with
ported_types + enrich — the seam real adapters land on in I1+
- devkitd: enrichment cached per 2s sweep (N clients never multiply parse
work); agent.enriched serves the cache
- daemon-client: enrichedAgents() + real-socket integration test
- agent-manager/src/fixtures: golden-bundle capture/replay — the parity
oracle for Rust↔TS equivalence; replay proves byte-identical output on a
live-captured claude bundle. Live bundles are gitignored (private
transcripts); committed synthetic fixtures land per-iteration.
Verified: cargo 20 tests, clippy/fmt clean, daemon-client 11, agent-manager
1312 (incl. fixture replay), tsc/oxlint clean.
I1 of the harness-port epic. devkitd now owns claude attribution: devkit-harness::claude ports the full detect path (resume/PID-file/ legacy matching + bounded head/tail JSONL parser + status/summary derivation), discover.rs gains batch lsof-cwd + ps-lstart enrichment, and agent.enriched reports ported=[claude]. AgentManager consumes daemon rows for ported types via injectable fetchEnrichedAgents with all-local fallback; cli wires it through ensureDaemon (implicit spawn). Fixture oracle extended with $NOW sentinel; committed synthetic bundles replay byte-identical on TS and Rust. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
I2 of the harness-port epic: CodexAdapter detection moves to Rust and the daemon reports ported: ["claude","codex"]. - codex/mod.rs: helper-subcommand filter with clap-accurate flag parsing (variadic --image, -- stop, app-server-daemon exclusion), sessions.json mapping, registry cache, locator, process-only fallback — TS stage/output order preserved. - codex/locator.rs: resume <uuid>, uuidv7 day-dir window, session_meta head parse with truncated-JSON regex fallback, legacy greedy match, 30s unmatched negative cache. - codex/parser.rs: bounded head/tail JSONL fold, session_meta gate, idle/waiting/running status, summary extraction. - shared.rs: birthtime_ms, local_day_key (libc localtime_r), is_idle, truncate, parse_timestamp_ms, fold_jsonl_bounded, read_file_head, registry_session_paths (readonly ~/.ai-devkit/agents.db). - Fixtures: bundles gain a `registry` field (seeded isolated agents.db on both replay sides); $TODAY normalization scoped to $NOW-start pids so live bundles replay on any day; live capture snapshots the registry pre-detect and includes .codex/ai-devkit. - Parity: synthetic matched/statuses bundles + live bundle (12 real agents) byte-identical on TS and Rust replay. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Third harness ported to Rust: daemon now reports ported ["claude","codex","pi"]. Thin consoles get identical enriched pi state from agent.enriched; AgentManager keeps local fallback for unported types. - pi/mod.rs: token-scan executable detection (pi/pi.js/pi.exe), sessions.json tracker with trusted-path gate, registry fallback (type='pi' readonly SQLite), detect pipeline in TS order - pi/locator.rs: encoded project dirs under ~/.pi/agent/sessions, filename-timestamp or birthtime candidacy, greedy <=3min match - pi/parser.rs: bounded head/tail fold, first-entry metadata sticky, filename session-id fallback, entry-free files -> None - shared.rs: fix is_idle float-division parity (ms > 300000) - capture: .pi/agent/sessions + sessions.json; pi REGISTRY_AWARE - fixtures: matched.json (tracker/registry/legacy/process-only/skip) + fallback.json (stale-type row, untrusted path, entry-free file) - live bundle replayed byte-identical TS/Rust; daemon smoke verified real pi pid 28617 enriched via agent.enriched Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- GeminiCliAdapter in devkit-harness: node-pool process filter, executable token scan (gemini/gemini.exe/gemini.js), sha256 project-hash + .project_root slug session discovery, .jsonl log replay (upsert/$set/$rewindTo) and legacy .json docs, wrapper registry-name override, registry cache, process-only fallback - daemon sweep now includes node/bun runtime argv0s so node-script harnesses reach adapters; visible pool gated on any_can_handle (mirrors isCandidateProcess) — also fixes pi-as-node-script gap - shared: wrapper-pid helpers + registry_agent_rows - fixtures: matched/fallback synthetic bundles + live bundle; atomic counter in materialize_home fixes parallel replay db collision Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- CopilotAdapter: inuse.<pid>.lock session attribution under ~/.copilot/session-state with dir-mtime bounded scan and the (pid,startTime) known-lock cache; events.jsonl bounded fold (session.start overrides, user/assistant text) + naive workspace.yaml fallback; wrapper registry-name override; process-only fallback - fixtures: matched/fallback/workspace synthetic bundles replay byte-identical TS vs Rust - TS capture: .copilot/session-state index dir; registry-aware replay Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- GrokAdapter: active_sessions.json pid->cwd resolution, latest-mtime session dir under sessions/<encodeURIComponent(cwd)>/, bounded chat_history.jsonl fold (<user_query> extraction, context injections skipped), idle/waiting/running from mtime + last role, process-only - shared: flatten_text_blocks, encode_uri_component (JS unreserved set) - fixture bundles gain a mtimes map (epoch ms | $NOW) applied at materialization — mtime-derived output (Grok lastActive, latest- session pick) replays deterministically; live capture records real file mtimes - fixtures: matched/fallback synthetic bundles, byte-identical both sides Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
SQLite-backed session attribution: readonly opencode.db lookup by proc cwd, session stats from message/part queries (heartbeat, completion, first user text), process-only fallback. Adds a 'sqlite' bundle section (SQL statements materialized on both replay sides) and pins XDG_DATA_HOME so replay never sees the real store. Fixes a latent processOnlyAgent crash on cwd-less processes (cwd ?? "") in shared.ts.
Lock-first session attribution: session_locks slugs map holder pid + ppid to session ids (devin acp backends excluded as agents), with sessions.working_directory fallback in sessions.db. Stats from message_nodes frontier/heartbeat and two-tier prompt summary. Bounded sqlite dump captures exactly the rows detection can reach; fixture replay accepts float mtimes.
Session locks under ~/.kiro/sessions/cli map holder pids to sessions via ppid-walk to the outermost kiro process (or sole-kiro-on-tty fallback); sibling .json/.jsonl supply metadata and a bounded transcript summary. Adds shared::list_dir_names — Node >=20.1 readdirSync returns sorted entries while raw read_dir does not; every safeReaddir mirror site now sorts (kiro's lock-ordered output exposed the divergence).
cwd-keyed last_conversations.json registry joins agy procs to their brain/<id> transcript; the bounded fold keeps MAX created_at, the last user/assistant role, and the USER_REQUEST prompt. Also fixes matches_executable_name to normalize .exe on candidate basenames (TS does both sides), and moves capture's index-dir rule to an explicit HARNESS_INDEX_DIRS so the 31MB brain tree stays referenced-only. All ten harnesses are now ported: claude, codex, pi, gemini_cli, copilot, grok_cli, opencode, devin, kiro, antigravity_cli.
The readiness checks + per-harness profiles now run inside the daemon: agent.readiness serves reports for all ten types (executable/config/ builtin-skills checks plus the claude/codex/copilot/devin/opencode/pi auth and integration probes). The TS injectable seams become a Host trait; fixtures/readiness bundles replay byte-identical between the TS oracle and a Rust FixtureHost. status.service is daemon-primary when no function seams are injected, with the local path as fallback.
All ten harness types are ported, so listAgents does zero local detection whenever devkitd answers — the TS adapters are now fallback only. channel-runner was the last consumer building a daemon-less AgentManager; it now wires fetchEnrichedAgents like commands/agent.ts. Metrics: 6,845 Rust LOC vs 9,850 TS across the ten adapters; 19 committed harness bundles + 2 readiness bundles replayed byte-identical; agent.enriched answers in 0.4ms warm vs ~238ms for a local sweep; devkitd idles at ~74MB RSS, ~7% CPU per 2s sweep.
Startup was dominated by module loading for commands that never ran: ink/react/ConsoleApp (~300ms), the init install stack (~330ms), and @ai-devkit/memory (~137ms) all loaded eagerly, plus every installed plugin entrypoint was imported on each invocation (~160ms). - cli.ts becomes a bootstrap: enables the V8 compile cache (Node >=22.8) before loading main.js, which keeps the program body unchanged. - init, memory, agent.service, @inquirer/prompts, ink/react/ConsoleApp, and session-compact are now imported inside the actions that need them. - Plugin registration is skipped when argv[2] is a built-in command or an early-exit flag; help/bare/plugin invocations still scan. Moved BUILT_IN_COMMAND_NAMES to a leaf module and added the missing builtins (setup, capacity, status, daemon). - devkitd: the enriched write lock was held for the entire sweep (*w = f() evaluates the guard first), stalling agent.enriched for the full ~800ms debug sweep. Compute outside the lock and run sweeps on spawn_blocking so they never occupy the async runtime. - vitest: pin AI_DEVKIT_NO_DAEMON in cli setup so a live developer daemon cannot stream events into subscription-based tests. agent list warm: ~630ms with 1.4-2.4s spikes -> ~290ms median.
ChannelConfigRepository cached a persistent DaemonClient whose socket was never closed. In the agent console the connection outlived unmount and pinned the event loop, so 'q' left the process running until Ctrl-C; one-shot commands like 'channel list' hung the same way. Scope each registry call to a connect/request/close pair.
readiness_reports ran all harness checks serially while spawning probes like 'opencode auth list' (~5s timeout) and 'pi list' (~4s) — the RPC took ~4.2s vs the TS path's Promise.all which finishes in ~max probe. Also dispatch ran inline on the async connection task, so a slow probe occupied a runtime worker. Checks and harnesses now fan out on scoped threads (Host gains a Sync bound), dispatch runs on spawn_blocking, and a 30s params-keyed cache serves warm 'status' calls in ~1ms instead of re-probing.
channels.json owns channel credentials; the daemon registry path is removed from ChannelConfigRepository along with its per-RPC client scoping — no daemon client means no socket to leak. The registry table and RPCs remain as generic infra for future non-secret state.
channel-bridges.json held live bridge process state — non-secret, multi-writer data with a read-modify-write race (getLiveBridges rewrote the file on every read, so a concurrent channel list could drop a freshly-registered bridge and orphan the process). The daemon registry scope "channel-bridges" replaces it: each mutation is an atomic registry.put/delete keyed by channel name. - getLiveBridges is now a pure read+pid-filter; dead entries prune on unregister instead of on every read - daemon-primary with file fallback; custom registryPath still selects file semantics for tests - listBridges unions file entries not present in the scope so a bridge registered under the old scheme stays visible during transition
seq is assigned under the store mutex but the broadcast send happened after emit() returned — two racing emits could deliver seq N+1 before seq N. A std mutex now guards store.emit + events.send together so subscribers observe events in seq order; required for seq-based dedup in the subscribe path.
The receiver was registered after the persisted replay finished, so events emitted in between were silently dropped. Subscribe first, then replay: overlapping events buffer in the receiver and are filtered by a per-connection delivery floor (seq > last_delivered), giving at-least-once delivery with no gap and no duplicates.
sweep() couldn't distinguish a failed ps from an empty process table — a transient failure applied an empty snapshot, deleting every agent row and storming disappeared/appeared events on the next good sweep. sweep now returns Option; apply_sweep keeps the previous snapshot on failure.
ps/lsof had no timeout — a wedged child would pin a blocking-pool thread and silently freeze discovery forever (stale enriched cache, dead event stream). run_with_timeout SIGKILLs the child so no zombie is left; a timed-out ps reads as a failed sweep and keeps the previous snapshot.
known.contains(p)/current.contains(p) made the per-sweep diff O(n^2). HashSet difference with sorted output keeps deterministic event order.
Two store fixes: - Agent identity is now (pid, start_time_ms): a recycled pid previously updated the dead process's row in place — no disappear/appear events, stale first_seen. Deletes stale rows before insert so first_seen resets; emits disappeared before appeared; adds column migration and a reuse regression test. - Events table bounded to the newest 10k rows; every 512 emits prunes below seq-10000 and checkpoints the WAL (was 4MB after a day), amortised off the insert hot path.
- Lagged broadcast subscribers get a subscription.lagged frame (seq 0) instead of silently missing history; clients refetch on it - Warn on previously silent failures: emit errors, snapshot apply errors, sweep panics, replay errors, abnormal conn close - Cheap RPCs (ping/daemon.status/agent.enriched) dispatch inline instead of paying a spawn_blocking pool hop - events.replay limit clamps to u32 instead of wrapping - Request bytes per connection capped at 4MiB - Corrupt daemon.db quarantined (with wal/shm) and recreated once instead of crash-looping devkitd on every autostart - run_command probes resolve against the client-supplied PATH — previously used the daemon's ambient PATH, which could report executable: ready while auth probes failed to find the binary - devkitd status probes the socket (listening / stale / not running) and install is gated to Linux (systemd unit is dead weight elsewhere) - data dir tightened to 0700 so wal/shm sidecars aren't world-readable
- Pending requests now reject when the socket dies (was: awaited
forever) and carry a 30s default timeout
- Persistent error/close listeners replace a consumable once("error")
that could leave later errors unhandled and crash the process
- close() drains pending instead of orphaning them
- New onDisconnect hook for callers that need to resubscribe
- Spawn lock now held until the socket answers — releasing right after
spawn() let a second caller spawn a twin devkitd that clobbered the
socket path and orphaned the first daemon
- daemon.log rotates to .1 past 1 MiB (was unbounded append)
- Dev binary resolution picks the freshest profile by mtime and uses
fileURLToPath instead of URL.pathname
The call-site gate in ab53abc left the fn unused on macOS, failing clippy -D warnings.
- subscribe accepts liveOnly — the console is a live UI; replaying the persisted event log (up to 10k frames) only triggered redundant refreshes - When the subscription socket dies (daemon restart) the console now resumes fast polling and retries attachDaemonRefresh after 2s via the client's onDisconnect hook, instead of silently staying on the 60s fallback forever
Now that all ten harness types are ported, the raw agent.list RPC and the ported[] coverage field carry no information: a successful agent.enriched answer is authoritative for every type. - agent.enriched returns a bare EnrichedAgent[] (no wrapper) - agent.list RPC removed; the agents table stays an internal diff ledger - AgentManager skips all local adapters when the daemon answers - regenerate bindings, update tests and docs
The qualified name only existed to distinguish the attributed view from the raw-snapshot RPC that no longer exists — one list API, one name. - wire method agent.enriched -> agent.list (dispatch, INLINE_METHODS) - DaemonClient.enrichedAgents -> listAgents; call sites updated - tests and design doc updated
agent.appeared/disappeared payloads were bare {pid}, forcing every
consumer to refetch to learn what actually changed. Payloads are now
{pid, agents: EnrichedAgent[]} — appeared resolves against the fresh
sweep cache, disappeared against the previous one (the row is already
gone from the new list, so this preserves last-known info). Unattributed
procs emit an empty agents array.
- events.replay RPC: no callers; subscribe's internal replay covers the at-least-once path - registry.changed: emitted on put/delete but nothing consumed it semantically — the console refreshes on any frame and registry writes don't affect the agent list Remaining kinds: agent.appeared/disappeared (console refresh signal) and subscription.lagged (subscriber plumbing).
The scoped registry's only consumer (channel-bridge bookkeeping) already kept a complete JSON-file path, and daemon rows had to be unioned with file rows at read time — two overlapping stores worse than one. - channel.service drops the daemon branch; channel-bridges.json is again the single store (mergeFileBridges/pruneDeadBridges/client() gone) - server drops registry.get/put/delete - store drops the registry table + methods; open() DROPs it for existing databases; concurrency test rewritten over emit/events_after - channel.service.daemon.test.ts removed with the path it covered
…iness Status probing is a rare, human-paced command — the daemon's only real win was a 30s cross-client cache, not worth the whole ported subsystem. - daemon: remove agent.readiness RPC, readiness cache, devkit-harness readiness module, devkit-core readiness types, gen bindings - client: status.service calls getAgentReadinessReports directly (local probes, seams preserved); DaemonClient.agentReadiness removed - daemon surface is now just what the daemon is for: agent.list, subscribe, lifecycle events
agent list was ~300-600ms of which ~85% was Node module resolution — ~200 files across cli + agent-manager + deps. The daemon made the work nearly free; boot was the floor. - esbuild bundle of src/cli.ts -> dist/bundle/cli.js (ESM, splitting): eager graph collapses to one ~285KB chunk; TUI/ink, init, session- compact stay as lazy chunks behind their existing dynamic imports - externals: better-sqlite3, yoga-layout (native/wasm), @ai-devkit/ memory (lazy + onnxruntime wasm), react-devtools-core (ink optional) - createRequire banner for CJS deps that call bare require() (telegraf) - copy agent-manager SQL migrations beside the bundle (resolved via dirname(import.meta.url)/migrations) - bin -> dist/bundle/cli.js; swc dist tree kept for library consumers agent list 550ms -> ~120-190ms; --version 240ms -> ~50ms
- drop unused regex/anyhow from devkit-harness deps - Store::list_agents is test-only since agent.list moved to the cache - daemon-client: remove dead connectDaemon alias + unused gen re-exports - delete harness-readiness lifecycle docs (feature removed this branch) - fix stale agent.enriched/ported references in kept port docs - fix stale comment in readiness-fixtures test (Rust side deleted)
registry.changed events were removed with the registry RPCs; the subscription test now exercises agent.disappeared instead.
serve() unconditionally unlinked the socket file before binding, so a second `devkitd serve` stole the path while the original kept sweeping and writing daemon.db invisibly. Probe the socket first: connectable means bail, refused means stale file — safe to unlink and rebind. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
resolveDaemonBinary already looked for @ai-devkit/devkitd-<plat>-<arch>,
but nothing built or published those packages — installs got the
fallback path and the daemon was dead code for end users. daemon-client
itself also had no publish workflow despite being a hard cli dep.
- npm/devkitd-{darwin,linux}-{arm64,x64}: os/cpu-gated package stubs,
version stamped from daemon-client at publish time
- publish-devkitd.yml: 4-target matrix; linux built musl-static so one
binary covers glibc and Alpine; darwin x64 cross-compiled on arm64
- publish-daemon-client.yml: injects optionalDependencies pins at
publish time so dev installs never resolve unpublished packages
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Conflicts resolved: - agent.ts deleted upstream (split into commands/agent/*.command.ts); daemon fetchEnrichedAgents wiring moved to factory.ts - cli.ts: kept the thin shim + compile cache; upstream program body already lives in main.ts — only the agent/index.js path updated - package.json: upstream 0.68.0 deps + daemon-client kept The upstream split eagerly pulled ink/react, telegraf and the TypeSafe SDK (undici) into the bundle's main chunk (285KB -> 4MB, --version 50ms -> 210ms). Re-lazified inside the action bodies: TUI via console.command.ts, session-compact classifier via session.command.ts, Telegraf via channel.ts. main chunk back to 289KB. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
fixtures/ was created during the harness port but never tracked — the parity tests assert at least one bundle, so CI's clean checkout fails rust:test without them. live*.json captures stay local per .gitignore.
The merge commit b447d45 was built from a mangled index — 173 files (the rust workspace, daemon-client, npm platform stubs, publish workflows, fixtures, lifecycle docs) never made it into the tree despite being on disk. Restores them verbatim; worktree content was never affected.
Several earlier commits were built from a swept index — their trees recorded stale blobs or skipped entries, so daemon-primary wiring, bundling metadata, subscription hook, the pi daemon-first store and merge resolutions never reached the pushed branch though the code ran locally. This commits the accumulated worktree state, which always held the true content. Includes the user's pending edits: status fixture seam passthrough and channel-runner's resolveAgentByName adoption.
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Moves
ai-devkitagent operations from per-command TS discovery to a machine-local Rust daemon (devkitd) — a tmux/dockerd-style architecture where the CLI is a thin client over a Unix socket.Daemon (
rust/crates/devkitd+devkit-core)~/.ai-devkit/daemon.sock; single daemon per machine (socket-ownership probe refuses double-serve)daemon.db): snapshot diff ledger + bounded append-only event log; corrupt-db quarantine + retryping,daemon.status,agent.list,subscribe(replay + live,liveOnly),shutdownagent.appeared/agent.disappearedcarrying full attributed agent rows,subscription.laggedEnrichedAgent[]cache —agent.listis an O(1) readHarness port (
rust/crates/devkit-harness) — all 10 adapters ported (Claude, Codex, Pi, Gemini, Copilot, Grok, OpenCode, Devin, Kiro, Antigravity) with committed-fixture parity tests against the TS implementations. TS adapters remain authoritative for conversation rendering, session browsing, readiness, and daemon-down fallback.CLI
agent list/detail/console consume daemon APIs; console refreshes viasubscribeevents with resubscribe-on-deathdist/bundle/cli.js, code-split lazy chunks) —agent list550–670ms → 120–190ms,--version~50msRelease plumbing
@ai-devkit/devkitd-{darwin,linux}-{arm64,x64}resolved viaoptionalDependencies(esbuild-style; Linux is musl-static covering glibc + Alpine)publish-devkitd.yml(4-target cargo matrix) andpublish-daemon-client.yml(injects platform pins at publish)Test plan
nx run-many -t lint— 8/8 projectsnx run-many -t test— all green (incl. 15 devkit-core + 63 devkit-harness + 6 devkitd Rust tests, fixture-parity vs TS)cargo clippy --workspace --all-targets -- -D warningscleanagent list~120–190ms bundled, subscribe live/replay verified end-to-end@ai-devkit/devkitd-darwin-x64@0.1.0publishes cleanly;x86_64-apple-darwincross-build compiles